1 /*
2 * Scope: a generic MVC framework.
3 * Copyright (c) 2000-2002, The Scope team
4 * All rights reserved.
5 *
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 *
11 * Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 *
14 * Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in the
16 * documentation and/or other materials provided with the distribution.
17 *
18 * Neither the name "Scope" nor the names of its contributors
19 * may be used to endorse or promote products derived from this software
20 * without specific prior written permission.
21 *
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
26 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
27 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
28 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
29 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
30 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
31 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
32 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
33 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 *
35 *
36 * $Id: STextField.java,v 1.17 2002/09/13 17:04:42 ludovicc Exp $
37 */
38 package org.scopemvc.view.swing;
39
40
41 import java.awt.event.ActionEvent;
42 import java.awt.event.ActionListener;
43 import java.awt.event.FocusEvent;
44 import java.awt.event.FocusListener;
45 import java.beans.Beans;
46 import javax.swing.JTextField;
47 import javax.swing.JToolTip;
48 import org.apache.commons.logging.Log;
49 import org.apache.commons.logging.LogFactory;
50 import org.scopemvc.core.Control;
51 import org.scopemvc.core.Controller;
52 import org.scopemvc.core.PropertyView;
53 import org.scopemvc.core.Selector;
54 import org.scopemvc.util.Debug;
55 import org.scopemvc.util.convertor.StringConvertor;
56 import org.scopemvc.util.convertor.StringConvertors;
57 import org.scopemvc.view.util.ModelBindable;
58
59 /***
60 * <P>
61 *
62 * A JTextField linked to a property of a bound model object. The property must
63 * have a StringConvertor to handle conversion to and from a String
64 * representation that will be edited in the textfield. Updates to the textfield
65 * result in changes to the model property when focus is lost. </P> <P>
66 *
67 * STextField responds to the bound model or the particular bound property
68 * becoming read-only by disabling itself. An STextField is also disabled if it
69 * has no bound model or property. </P> <P>
70 *
71 * STextField can issue a Control when the user hits the Enter key. </P> <P>
72 *
73 * Null properties are handled in one of two ways:
74 * <OL>
75 * <LI> The field is disabled to prevent editing. </LI>
76 * <LI> The field is populated with an empty String. In this case, the bound
77 * property will contain an empty String not a null, if the textfield is
78 * populated with an empty string. </LI>
79 * </OL>
80 * The second option is the default but that can be changed by calling {@link
81 * #setDisableOnNull}. </P>
82 *
83 * @author <A HREF="mailto:smeyfroi@users.sourceforge.net">Steve Meyfroidt</A>
84 * @created 05 September 2002
85 * @version $Revision: 1.17 $ $Date: 2002/09/13 17:04:42 $
86 * @see SwingView
87 */
88 public class STextField extends JTextField implements PropertyView, FocusListener, ActionListener, ModelBindable, Refreshable {
89
90 private static final Log LOG = LogFactory.getLog(STextField.class);
91
92 /***
93 * Helper to manage model to view binding.
94 */
95 private SwingBoundModel boundModel = new SwingBoundModel(this);
96
97 /***
98 * Helper to manage validation state.
99 */
100 private ValidationHelper validationHelper = new ValidationHelper(this);
101
102 /***
103 * ID of the Control issued when user hits Enter in the STextField.
104 */
105 private String controlID;
106
107 /***
108 * STextField can "hold" a null when bound to a property that happens to be
109 * null.
110 */
111 private boolean valueIsNull = false;
112
113 /***
114 * Does this textfield disable itself if the model property it is bound to
115 * becomes null?
116 */
117 private boolean disableOnNull = false;
118
119 /***
120 * The StringConvertor used to convert the model property to and from the
121 * String representation that the user edits in the textfield.
122 */
123 private StringConvertor stringConvertor;
124
125 /***
126 * If this is set, use it instead of finding a convertor to match the
127 * datatype being edited.
128 */
129 private StringConvertor forcedStringConvertor;
130
131
132 /***
133 * Constructor for the STextField object
134 */
135 public STextField() {
136 this(12);
137 }
138
139
140 /***
141 * Constructor for the STextField object
142 *
143 * @param columns TODO: Describe the Parameter
144 */
145 public STextField(int columns) {
146 super(columns);
147 setEnabled(Beans.isDesignTime());
148 addFocusListener(this);
149 addActionListener(this);
150 }
151
152
153 /***
154 * @return true if the textfield should be disabled when it edits a null
155 * property value. Otherwise, a null property is treated as an empty
156 * String.
157 */
158 public final boolean isDisableOnNull() {
159 return disableOnNull;
160 }
161
162
163 // ------------------- Delegate to BoundModel -------------------
164
165 /***
166 * Gets the bound model
167 *
168 * @return The boundModel value
169 */
170 public final Object getBoundModel() {
171 return boundModel.getBoundModel();
172 }
173
174
175 /***
176 * Gets the selector
177 *
178 * @return The selector value
179 */
180 public final Selector getSelector() {
181 return boundModel.getSelector();
182 }
183
184
185 // ---------- Issue a Control when user hits Enter in the STextfield --------------
186
187 /***
188 * Gets the control ID
189 *
190 * @return The controlID value
191 */
192 public final String getControlID() {
193 return controlID;
194 }
195
196
197 /***
198 * Get the current value (what would be set as a property of the bound model
199 * object) being presented on the View.
200 *
201 * @return property value from parsing the textfield's current String
202 * representation.
203 * @exception IllegalArgumentException if the conversion from String fails.
204 */
205 public Object getViewValue() throws IllegalArgumentException {
206 String text = getText();
207
208 if (stringConvertor == null
209 || valueIsNull
210 || (isDisableOnNull() && text.length() < 1)) {
211 return null;
212 }
213
214 return stringConvertor.stringAsValue(text);
215 }
216
217
218 /***
219 * Don't assign a Controller to STextField, instead delegate to the
220 * containing SwingView that has a parent Controller.
221 *
222 * @return The controller value
223 */
224 public Controller getController() {
225 return null;
226 }
227
228
229 /***
230 * Don't assign a Controller to STextField, instead delegate to the
231 * containing SwingView that has a parent Controller.
232 *
233 * @param inControl TODO: Describe the Parameter
234 */
235 public void issueControl(Control inControl) {
236 SwingUtil.issueControl(this, inControl);
237 }
238
239
240 /***
241 * Set this to true if the textfield should be disabled when it edits a null
242 * property value. Otherwise, a null property is treated as an empty String.
243 *
244 * @param inDisable The new disableOnNull value
245 */
246 public final void setDisableOnNull(boolean inDisable) {
247 disableOnNull = inDisable;
248 }
249
250
251 /***
252 * Sets the bound model
253 *
254 * @param inModel The new boundModel value
255 */
256 public final void setBoundModel(Object inModel) {
257 boundModel.setBoundModel(inModel);
258 setupStringConvertor();
259 }
260
261
262 /***
263 * Sets the selector
264 *
265 * @param inSelector The new selector value
266 */
267 public final void setSelector(Selector inSelector) {
268 boundModel.setSelector(inSelector);
269 setupStringConvertor();
270 }
271
272
273 /***
274 * Sets the selector string
275 *
276 * @param inSelectorString The new selectorString value
277 */
278 public final void setSelectorString(String inSelectorString) {
279 boundModel.setSelectorString(inSelectorString);
280 setupStringConvertor();
281 }
282
283 /***
284 * Set the ID of the Control that will be issued when Enter key is pressed
285 * in this STextField. If null no Control will be issued.
286 *
287 * @param inControlID The new controlID value
288 */
289 public final void setControlID(String inControlID) {
290 controlID = inControlID;
291 }
292
293
294 /***
295 * Force use of this StringConvertor instead of automatically finding one to
296 * match the datatype being edited.
297 *
298 * @param inConvertor The new stringConvertor value
299 */
300 public void setStringConvertor(StringConvertor inConvertor) {
301 forcedStringConvertor = inConvertor;
302 }
303
304
305 /***
306 * Override to call super.setText() only if new value not equals() old
307 * value.
308 *
309 * @param t new text.
310 */
311 public void setText(String t) {
312 if (Debug.ON) {
313 Debug.assertTrue(getText() != null, "null getText()");
314 }
315 if (!getText().equals(t)) {
316 super.setText(t);
317 }
318 setCaretPosition(0);
319 }
320
321 /***
322 * Don't assign a Controller to this component, instead delegate to the
323 * containing SwingView that has a parent Controller.
324 *
325 * @param inController The new controller value
326 */
327 public void setController(Controller inController) {
328 throw new UnsupportedOperationException("Can't assign a Controller to a " + getClass());
329 }
330
331 // --------------------- Implement ModelBindable ----------------------
332
333 /***
334 * Converts the incoming value to a String via appropriate {@link
335 * org.scopemvc.util.convertor.StringConvertor}. For incoming null either
336 * disable field or set text to empty String.
337 *
338 * @param inValue TODO: Describe the Parameter
339 * @param inReadOnly TODO: Describe the Parameter
340 */
341 public void updateFromProperty(Object inValue, boolean inReadOnly) {
342 if (LOG.isDebugEnabled()) {
343 LOG.debug("updateFromProperty: " + inValue + ", " + inReadOnly);
344 }
345
346 // ***** Hack. Need to revisit PropertyManager to traverse nulls and use metadata etc
347 setupStringConvertor();
348
349 if (stringConvertor == null) {
350 valueIsNull = true;
351 // ?
352 setEnabled(false);
353 setText("");
354 return;
355 }
356
357 if (inValue == null && isDisableOnNull()) {
358 valueIsNull = true;
359 setEnabled(false);
360 setText("");
361 return;
362 }
363
364 valueIsNull = false;
365 //(inValue == null);
366 setEnabled(!inReadOnly);
367 try {
368 String text = stringConvertor.valueAsString(inValue);
369 setText(text);
370 } catch (IllegalArgumentException e) {
371 // should never happen normally -- comes from getValue() but the
372 // ... property value must always be convertible to String?
373 LOG.error("updateFromProperty", e);
374 setEnabled(false);
375 }
376 }
377
378
379 /***
380 * TODO: document the method
381 *
382 * @param inException TODO: Describe the Parameter
383 */
384 public void validationFailed(Exception inException) {
385 validationHelper.validationFailed(inException);
386 }
387
388
389 /***
390 * TODO: document the method
391 */
392 public void validationSuccess() {
393 validationHelper.validationSuccess();
394 }
395
396
397 /***
398 * TODO: document the method
399 *
400 * @return TODO: Describe the Return Value
401 */
402 public JToolTip createToolTip() {
403 return validationHelper.createToolTip(super.createToolTip());
404 }
405
406
407 // ---------------------- View to model ----------------------
408
409 /***
410 * TODO: document the method
411 *
412 * @param inEvent TODO: Describe the Parameter
413 */
414 public void focusLost(FocusEvent inEvent) {
415 if (LOG.isDebugEnabled()) {
416 LOG.debug("focusLost");
417 }
418
419 if (!inEvent.isTemporary()) {
420 boundModel.updateModel();
421 if (controlID != null) {
422 Control control = new Control(controlID);
423 issueControl(control);
424 }
425 }
426 }
427
428
429 /***
430 * TODO: document the method
431 *
432 * @param inEvent TODO: Describe the Parameter
433 */
434 public void focusGained(FocusEvent inEvent) { }
435
436
437 /***
438 * Sync model object to View and issue a Control if ControlID set.
439 *
440 * @param e TODO: Describe the Parameter
441 */
442 public void actionPerformed(ActionEvent e) {
443 boundModel.updateModel();
444 // ... then do the sync to model
445
446 if (controlID != null) {
447 Control control = new Control(controlID);
448 issueControl(control);
449 }
450 }
451
452
453 // ------------------ Refreshable -------------------------
454
455 /***
456 * TODO: document the method
457 */
458 public void refresh() {
459 Object propertyValue = boundModel.getPropertyValue();
460 boolean propertyReadOnly = boundModel.getPropertyReadOnly();
461 updateFromProperty(propertyValue, propertyReadOnly);
462 }
463
464
465 /***
466 * TODO: document the method
467 */
468 protected void setupStringConvertor() {
469 if (forcedStringConvertor != null) {
470 stringConvertor = forcedStringConvertor;
471 return;
472 }
473
474 try {
475 Object m = getBoundModel();
476 Selector s = getSelector();
477 if (m != null) {
478 Class clazz = boundModel.getPropertyManager().getPropertyClass(m, s);
479 stringConvertor = StringConvertors.forClass(clazz);
480 }
481 } catch (Exception e) {
482 LOG.warn("setupStringConvertor", e);
483 stringConvertor = null;
484 }
485 }
486 }
487
This page was automatically generated by Maven